package com.niiwoo.civet.user.service.duboo;

import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.niiwoo.activity.event.constant.BizEventRabbitConstant;
import com.niiwoo.activity.event.dto.request.UserRealNameReqDTO;
import com.niiwoo.civet.trade.dto.response.OcrPortraitAuthConfigResponseDTO;
import com.niiwoo.civet.trade.service.configure.TradeConfigDubboService;
import com.niiwoo.civet.user.constants.LoginAccountErrorCode;
import com.niiwoo.civet.user.dao.entity.*;
import com.niiwoo.civet.user.dto.request.AssetPortraitAuthRequestDTO;
import com.niiwoo.civet.user.dto.request.PortraitAuthRequestDTO;
import com.niiwoo.civet.user.dto.request.portraitAuthStatusResponseDTO;
import com.niiwoo.civet.user.dto.response.PortraitAuthResponseDTO;
import com.niiwoo.civet.user.dto.response.SearchFacePlusPortraitResponseDTO;
import com.niiwoo.civet.user.enums.*;
import com.niiwoo.civet.user.service.PortraitAuthDubboService;
import com.niiwoo.civet.user.service.UserForbidDubboService;
import com.niiwoo.civet.user.service.local.*;
import com.niiwoo.tripod.base.component.AsyncHttpClients;
import com.niiwoo.tripod.base.component.PrivacyMasks;
import com.niiwoo.tripod.im.component.IMService;
import com.niiwoo.tripod.im.request.UpdateUserInfoRequest;
import com.niiwoo.tripod.provider.exception.BizException;
import com.niiwoo.tripod.service.component.SnowflakeIdWorker;
import com.niiwoo.tripod.tiancheng.component.TianchengAuthFaceIdService;
import com.niiwoo.tripod.tiancheng.enums.ComparisonTypeEnum;
import com.niiwoo.tripod.tiancheng.enums.FaceImageTypeEnum;
import com.niiwoo.tripod.tiancheng.exception.FaceAuthException;
import com.niiwoo.tripod.tiancheng.request.auth.faceid.FaceIdVerifyReq;
import com.niiwoo.tripod.tiancheng.response.auth.faceid.FaceIdVerifyResp;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.util.EntityUtils;
import org.joda.time.Duration;
import org.joda.time.LocalDate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.stream.Stream;

import static com.niiwoo.civet.user.service.local.LoginAccountRedisService.PORTRAIT_AUTH_ERROR_TIMES_FORMAT;

/**
 * 肖像认证API实现
 */
@Service(version = "1.0.0")
public class PortraitAuthDubboServiceImpl implements PortraitAuthDubboService {

    public static final Logger logger = LoggerFactory.getLogger(PortraitAuthDubboServiceImpl.class);

    @Autowired
    private TianchengAuthFaceIdService tianchengAuthFaceIdService;

    @Autowired
    UserImageFileService userImageFileService;

    @Autowired
    UserStatusService userStatusService;

    @Autowired
    FacePlusPortraitService facePlusPortraitService;

    @Autowired
    SnowflakeIdWorker snowflakeIdWorker;

    @Autowired
    UserIdentityService userIdentityService;
    @Autowired
    PrivacyMasks privacyMasks;
    @Autowired
    IdentityMappingService identityMappingService;
    @Reference(version = "1.0.0")
    private TradeConfigDubboService tradeConfigDubboService;
    @Autowired
    private UserAuthorizationInfoService userAuthorizationInfoService;
    @Autowired
    private MobileIdentityService mobileIdentityService;
    @Autowired
    AsyncHttpClients asyncHttpClients;

    @Autowired
    PortraitStatisticsService portraitStatisticsService;

    @Autowired
    UserForbidDubboService userForbidDubboService;

    @Autowired
    IMService imService;

    @Autowired
    RabbitTemplate rabbitTemplate;

    private CompletableFuture<byte[]> downloadFile(String url) {
        CompletableFuture<byte[]> future = new CompletableFuture<>();
        asyncHttpClients.getRequest(url, new FutureCallback<HttpResponse>() {
            @Override
            public void completed(HttpResponse response) {
                try {
                    int code = response.getStatusLine().getStatusCode();
                    byte[] file = null;
                    if (code == org.apache.http.HttpStatus.SC_OK) {
                        file = EntityUtils.toByteArray(response.getEntity());
                    }
                    if (file == null || file.length == 0) {
                        logger.error("肖像认证###下载图片失败, url={}", url);
                        throw new BizException("下载图片失败");
                    }
                    future.complete(file);
                } catch (Exception e) {
                    future.completeExceptionally(e);
                }
            }

            @Override
            public void failed(Exception e) {
                future.completeExceptionally(e);
            }

            @Override
            public void cancelled() {
                future.cancel(true);
            }
        });

        return future;
    }

    /**
     * 校验: 1. 已OCR 2. 未肖像认证 3. 可认证次数大于零
     *
     * @param userId 用户 id
     * @return 肖像认证剩余次数
     */
    private int verifyOCRAuthedAndPortraitNotAuth(String userId) {
        if (userForbidDubboService.queryValidUserForbidInfo(userId, UserForbidTypeEnum.IDENTITY) != null) {
            throw new BizException(LoginAccountErrorCode.IDENTITY_AUTH_FORBID);
        }
        List<DataStatus> list = Optional.ofNullable(userStatusService.getDataStatusByUserId(userId)).orElse(Collections.EMPTY_LIST);
        boolean isOCRAuth = list.stream()
                .anyMatch(s ->
                        Objects.equals(s.getDataType(), UserDataTypeEnum.OCR.getValue()) &&
                                Objects.equals(s.getDataStatus(), UserDataStatusEnum.OCRStatus.YES_OCR.getStatus())
                );
        if (!isOCRAuth) {
            throw new BizException(LoginAccountErrorCode.NEED_OCR);
        }
        boolean isPortraitAuth = list.stream()
                .anyMatch(s ->
                        Objects.equals(s.getDataType(), UserDataTypeEnum.PORTRAIT.getValue()) &&
                                UserDataStatusEnum.PortraitAuthStatus.except(s.getDataStatus(), UserDataStatusEnum.PortraitAuthStatus.PASS_CERTIFIED, UserDataStatusEnum.PortraitAuthStatus.NEED_APPROVE)
                );
        if (isPortraitAuth) {
            throw new BizException(LoginAccountErrorCode.PORTRAIT_AUTH_HAS_FINISHED_OR_CHECKING);
        }
        // 认证错误次数达到3次以上
        int cacheErrorTimes = facePlusPortraitService.getCacheValue(PORTRAIT_AUTH_ERROR_TIMES_FORMAT, new Object[]{userId})
                .map(Integer::valueOf)
                .orElse(0);
        if (cacheErrorTimes >= getPortraitAuthTimesThreshold()) {
             throw new BizException(LoginAccountErrorCode.OVER_PORTRAIT_AUTH_ERROR_TIMES);
        }
        return getPortraitAuthTimesThreshold() - cacheErrorTimes;
    }

    /**
     * 获取肖像认证信息
     */
    @Override
    public portraitAuthStatusResponseDTO getStatusInfo(String userId) {
        int leftTimes = verifyOCRAuthedAndPortraitNotAuth(userId);
        portraitAuthStatusResponseDTO responseDTO = new portraitAuthStatusResponseDTO();
        responseDTO.setLeftPortraitAuthTimes(leftTimes);
        responseDTO.setPortraitAuthTimesThreshold(getPortraitAuthTimesThreshold());
        return responseDTO;
    }

    /**
     * 肖像认证失败次数阈值
     */
    private int getPortraitAuthTimesThreshold() {
        return 3;
    }

    /**
     * 肖像认证
     */
    @Override
    public PortraitAuthResponseDTO auth(PortraitAuthRequestDTO requestDTO) {
        verifyOCRAuthedAndPortraitNotAuth(requestDTO.getUserId());
        // 查询正面照
        UserImageFile frontImage = getFrontImage(requestDTO.getUserId());
        OcrPortraitAuthConfigResponseDTO configDTO = Optional.ofNullable(tradeConfigDubboService.queryOcrPortraitConfig())
                .orElseThrow(() -> new BizException(LoginAccountErrorCode.LOAD_OCR_CONFIG_FAIL));
        IdentityInfo identityInfo = userIdentityService.getByUserId(requestDTO.getUserId());
        switch (requestDTO.getChannel()) {
            case FACE_PLUS:
                return handleFacePlus(requestDTO, frontImage, identityInfo, configDTO);
            default:
                throw new BizException(LoginAccountErrorCode.NOT_SUPPORT_PORTRAIT_CHANNEL);
        }
    }

    @Override
    public PortraitAuthResponseDTO authAsset(AssetPortraitAuthRequestDTO requestDTO) {
        verifyOCRAuthedAndPortraitNotAuth(requestDTO.getUserId());

        // 查询正面照
        UserImageFile frontImage = getFrontImage(requestDTO.getUserId());

        OcrPortraitAuthConfigResponseDTO configDTO;
        switch (requestDTO.getSourceFromEnum()) {
            case R360:
                configDTO = Optional.ofNullable(tradeConfigDubboService.queryR360PortraitConfig())
                        .orElseThrow(() -> new BizException(LoginAccountErrorCode.LOAD_OCR_CONFIG_FAIL));
                break;
            default:
                configDTO = Optional.ofNullable(tradeConfigDubboService.queryOcrPortraitConfig())
                        .orElseThrow(() -> new BizException(LoginAccountErrorCode.LOAD_OCR_CONFIG_FAIL));
        }

        IdentityInfo identityInfo = userIdentityService.getByUserId(requestDTO.getUserId());
        switch (requestDTO.getChannel()) {
            case FACE_PLUS:
                return handleAssetFacePlus(requestDTO, frontImage, identityInfo, configDTO);
            default:
                throw new BizException(LoginAccountErrorCode.NOT_SUPPORT_PORTRAIT_CHANNEL);
        }
    }

    // 0: 更新失败， 1：更新成功
    @Override
    public int updateBestImg4R360(AssetPortraitAuthRequestDTO requestDTO){
        return facePlusPortraitService.updateBestImg4R360(requestDTO);
    }

    // 0: 更新失败， 1：更新成功
    @Override
    public int updatePortraitAuthStatus2NeedApprove4R360(String userId){
        return facePlusPortraitService.updatePortraitAuthStatus2NeedApprove4R360(userId);
    }

    private  UserImageFile getFrontImage(String userId) {
       return Optional.ofNullable(userImageFileService.getByType(userId, UserImgTypeEnum.IDCARD_IMAGE, UserImgTypeEnum.IDCARD_FRONT_IMAGE))
                .orElseThrow(() -> new BizException(LoginAccountErrorCode.MISS_ID_CARD_FRONT_IMAGE));
    }

    @Override
    public SearchFacePlusPortraitResponseDTO searchFacePlusPortraitResponse(String userId) {
        FacePlusPortrait facePlusPortrait = facePlusPortraitService.getFacePlusPortraitByUserId(userId);
        if(facePlusPortrait==null){
            return null;
        }
        SearchFacePlusPortraitResponseDTO searchFacePlusPortraitResponseDTO = new SearchFacePlusPortraitResponseDTO();
        searchFacePlusPortraitResponseDTO.setUserId(facePlusPortrait.getUserId());
        searchFacePlusPortraitResponseDTO.setBestLiveImage(facePlusPortrait.getBestLiveImage());
        searchFacePlusPortraitResponseDTO.setLiveImageConfidence(facePlusPortrait.getLiveImageConfidence());
        searchFacePlusPortraitResponseDTO.setSyntheticFaceConfidence(facePlusPortrait.getSyntheticFaceConfidence());
        searchFacePlusPortraitResponseDTO.setSyntheticFaceThreshold(facePlusPortrait.getSyntheticFaceThreshold());
        searchFacePlusPortraitResponseDTO.setMaskThreshold(facePlusPortrait.getMaskThreshold());
        searchFacePlusPortraitResponseDTO.setScreenReplayConfidence(facePlusPortrait.getScreenReplayConfidence());
        searchFacePlusPortraitResponseDTO.setScreenReplayThreshold(facePlusPortrait.getScreenReplayThreshold());
        searchFacePlusPortraitResponseDTO.setIdImageConfidence(facePlusPortrait.getIdImageConfidence());
        searchFacePlusPortraitResponseDTO.setIdImageQuality(facePlusPortrait.getIdImageQuality());
        searchFacePlusPortraitResponseDTO.setIdImageQualityThreshold(facePlusPortrait.getIdImageQualityThreshold());
        searchFacePlusPortraitResponseDTO.setIdCardAttacked(facePlusPortrait.getIdCardAttacked());
        UserDataStatusEnum.PortraitAuthStatus portraitAuthStatus = Optional.ofNullable(userStatusService.getByUserIdAndType(userId, UserDataTypeEnum.PORTRAIT))
                .map(s -> UserDataStatusEnum.PortraitAuthStatus.of(s.getDataStatus()))
                .orElse(UserDataStatusEnum.PortraitAuthStatus.NOT_CERTIFIED);
        searchFacePlusPortraitResponseDTO.setAuditStatus(portraitAuthStatus.getStatus());

        searchFacePlusPortraitResponseDTO.setCreateTime(new Date());
        searchFacePlusPortraitResponseDTO.setUpdateTime(new Date());

        return searchFacePlusPortraitResponseDTO;
    }

    /**
     * 资产端face++ 比对
     */
    private PortraitAuthResponseDTO handleAssetFacePlus(AssetPortraitAuthRequestDTO requestDTO, UserImageFile frontImage, IdentityInfo identityInfo, OcrPortraitAuthConfigResponseDTO configDTO) {
        final PortraitAuthResponseDTO portraitAuthResponseDTO = new PortraitAuthResponseDTO();

        // 创建历史认证记录
        FacePlusPortraitRecord record = initFacePlusPortraitRecord(requestDTO.getUserId(), requestDTO.getLiveImageUrl(), frontImage.getImgUrl(), requestDTO.getDelta(), requestDTO.getClientType());

        //构造最佳人脸响应
        FaceIdVerifyResp verifyResp = getAssetFaceImageVerifyResp(requestDTO);

        //最佳人脸比对
        FacePlusPortraitRecordExt liveImageRecord = getFacePlusImageHandleFunction(true, configDTO).apply(verifyResp, null);
        FacePlusPortraitRecordExt frontImageRecord = new FacePlusPortraitRecordExt();
        frontImageRecord.setIdImageAuthStatus(FacePlusAuditStatusEnum.INIT.getValue());
        frontImageRecord.setIdImage(frontImage.getImgUrl());

        if (Objects.nonNull(requestDTO.getIdConfidence())) {
            FaceIdVerifyResp idImageVerifyResp = getAssetIdImageVerifyResp(requestDTO);
            frontImageRecord = getFacePlusImageHandleFunction(false, configDTO).apply(idImageVerifyResp, null);
        }
        //通过才进行身份证照片验证
        else if (!liveImageRecord.getLiveImageAuthStatus().equals(FacePlusAuditStatusEnum.FAILURE.getValue())) {

            // 对比身份证正面照请求
            FaceIdVerifyReq facePlusIdImageRequest = createFacePlusIdImageRequest(identityInfo, frontImage.getImgUrl());

            //  身份证照片结果处理
            BiFunction<FaceIdVerifyResp, Throwable, FacePlusPortraitRecordExt> facePlusIdImageHandleFunction = getFacePlusImageHandleFunction(false, configDTO);

            // 对比身份证照片
            frontImageRecord = tianchengAuthFaceIdService.verify(facePlusIdImageRequest).handle(facePlusIdImageHandleFunction).join();

        }
        // 整合两次对比结果
        getCombinedHandleFunction(requestDTO.getUserId(), identityInfo, portraitAuthResponseDTO, record).accept(liveImageRecord,frontImageRecord);
        return portraitAuthResponseDTO;
    }

    /**
     * 构造资产端最佳人脸响应.
     * 最佳人脸比对，资产端会将分数和阈值传过来。不再调face++做最佳人脸的比对<p>
     * 所以防hack直接用固定值规避
     *
     * @param requestDTO 资产端请求
     * @return 最佳人脸比对响应
     */
    private FaceIdVerifyResp getAssetFaceImageVerifyResp(AssetPortraitAuthRequestDTO requestDTO) {
        FaceIdVerifyResp verifyResp = new FaceIdVerifyResp();

        FaceIdVerifyResp.ResultFaceid resultFaceid =  new FaceIdVerifyResp.ResultFaceid();
        resultFaceid.setConfidence(Float.valueOf(requestDTO.getConfidence()));

        FaceIdVerifyResp.Thresholds thresholds =  JSON.parseObject(requestDTO.getThresholds(),FaceIdVerifyResp.Thresholds.class);
        resultFaceid.setThresholds(thresholds);
        verifyResp.setResultFaceid(resultFaceid);

        FaceIdVerifyResp.FaceGenuineness faceGenuineness = new FaceIdVerifyResp.FaceGenuineness();
        faceGenuineness.setMaskConfidence(0.1f);
        faceGenuineness.setMaskThreshold(0.5f);
        faceGenuineness.setScreenReplayConfidence(0.1f);
        faceGenuineness.setScreenReplayThreshold(0.5f);
        faceGenuineness.setSyntheticFaceConfidence(0.1f);
        faceGenuineness.setSyntheticFaceThreshold(0.5f);
        faceGenuineness.setFaceReplaced(0);
        verifyResp.setFaceGenuineness(faceGenuineness);
        return verifyResp;
    }

    /**
     * 构造资产端身份证正面照人脸响应.
     * 最佳人脸比对，资产端会将分数和阈值传过来。不再调face++做最佳人脸的比对<p>
     * 所以防hack直接用固定值规避
     *
     * @param requestDTO 资产端请求
     * @return 最佳人脸比对响应
     */
    private FaceIdVerifyResp getAssetIdImageVerifyResp(AssetPortraitAuthRequestDTO requestDTO) {
        FaceIdVerifyResp verifyResp = new FaceIdVerifyResp();

        FaceIdVerifyResp.ResultFaceid resultFaceid =  new FaceIdVerifyResp.ResultFaceid();
        resultFaceid.setConfidence(Float.valueOf(requestDTO.getIdConfidence()));

        FaceIdVerifyResp.Thresholds thresholds =  JSON.parseObject(requestDTO.getIdThresholds(),FaceIdVerifyResp.Thresholds.class);
        resultFaceid.setThresholds(thresholds);
        verifyResp.setResultFaceid(resultFaceid);

        List<FaceIdVerifyResp.Faces> facesList = new ArrayList<>();
        FaceIdVerifyResp.Faces faces = new FaceIdVerifyResp.Faces();
        faces.setQuality(100f);
        faces.setQualityThreshold(50f);
        facesList.add(faces);
        verifyResp.setFaces(facesList);

        FaceIdVerifyResp.IdExceptions idExceptions = new FaceIdVerifyResp.IdExceptions();
        idExceptions.setIdAttacked(0);
        verifyResp.setIdExceptions(idExceptions);
        return verifyResp;
    }

    /**
     * face++肖像认证
     */
    private PortraitAuthResponseDTO handleFacePlus(PortraitAuthRequestDTO requestDTO, UserImageFile frontImage, IdentityInfo identityInfo, OcrPortraitAuthConfigResponseDTO configDTO) {
        final PortraitAuthResponseDTO portraitAuthResponseDTO = new PortraitAuthResponseDTO();
        // 创建历史认证记录
        FacePlusPortraitRecord record = initFacePlusPortraitRecord(requestDTO.getUserId(), requestDTO.getLiveImageUrl(), frontImage.getImgUrl(), requestDTO.getDelta(), requestDTO.getClientType());
        // 对比活体照片请求
        FaceIdVerifyReq facePlusLiveImageRequest = createFacePlusLiveImageRequest(identityInfo, requestDTO.getDelta(), requestDTO.getLiveImageUrl(), requestDTO.getFacePlusEnvImageUrl());
        //  对比活体照片结果处理
        BiFunction<FaceIdVerifyResp, Throwable, FacePlusPortraitRecordExt> facePlusLiveImageHandleFunction = getFacePlusImageHandleFunction(true, configDTO);
        // 对比身份证正面照请求
        FaceIdVerifyReq facePlusIdImageRequest = createFacePlusIdImageRequest(identityInfo, frontImage.getImgUrl());
        //  身份证照片结果处理
        BiFunction<FaceIdVerifyResp, Throwable, FacePlusPortraitRecordExt> facePlusIdImageHandleFunction = getFacePlusImageHandleFunction(false, configDTO);
        // 对比最佳人脸照片
        CompletableFuture<FacePlusPortraitRecordExt> liveImageFuture = tianchengAuthFaceIdService.verify(facePlusLiveImageRequest).handle(facePlusLiveImageHandleFunction);
        // 对比身份证照片
        CompletableFuture<FacePlusPortraitRecordExt> frontImageFuture = tianchengAuthFaceIdService.verify(facePlusIdImageRequest).handle(facePlusIdImageHandleFunction);
        // 整合两次对比结果
        BiConsumer<FacePlusPortraitRecordExt, FacePlusPortraitRecordExt> CombinedHandleFunction = getCombinedHandleFunction(requestDTO.getUserId(), identityInfo, portraitAuthResponseDTO, record);
        // 必须同步等待返回
        liveImageFuture.thenAcceptBothAsync(frontImageFuture, CombinedHandleFunction).join();
        return portraitAuthResponseDTO;
    }

    private boolean isNoFaceFound(String errorMessage) {
        return StringUtils.isNotBlank(errorMessage) && Stream.of("IMAGE_ERROR_UNSUPPORTED_FORMAT", "NO_FACE_FOUND", "DATA_VALIDATION_ERROR").anyMatch(errorMessage::startsWith);
    }

    private boolean isAuthFail(String errorMessage) {
        return StringUtils.isNotBlank(errorMessage) && Stream.of("IMAGE_ERROR_UNSUPPORTED_FORMAT", "NO_FACE_FOUND", "INVALID_FACE_TOKEN").anyMatch(errorMessage::startsWith);
    }

    private boolean isNotMatchIdentity(String errorMessage) {
        return StringUtils.isNotBlank(errorMessage) && Stream.of("NO_SUCH_ID_NUMBER", "ID_NUMBER_NAME_NOT_MATCH", "INVALID_NAME_FORMAT", "INVALID_IDCARD_NUMBER").anyMatch(errorMessage::startsWith);
    }

    private boolean isChannelException(String errorMessage) {
        return StringUtils.isNotBlank(errorMessage) && Stream.of("DATA_SOURCE_ERROR").anyMatch(errorMessage::startsWith);
    }

    /**
     * 根据两次图片对比结果，判断最终肖像认证状态
     *
     * @param liveImageStatusValue 活体照对比状态
     * @param idImageStatusValue 身份证照对比状态
     */
    private UserDataStatusEnum.PortraitAuthStatus getCombinedPortraitAduitStatus(Byte liveImageStatusValue, Byte idImageStatusValue) {
        FacePlusAuditStatusEnum liveImageStatus = FacePlusAuditStatusEnum.of(liveImageStatusValue);
        FacePlusAuditStatusEnum idImageStatus = FacePlusAuditStatusEnum.of(idImageStatusValue);
        boolean isBothSuccess = liveImageStatus == FacePlusAuditStatusEnum.SUCCESS && idImageStatus == FacePlusAuditStatusEnum.SUCCESS;
        if (isBothSuccess) {
            return UserDataStatusEnum.PortraitAuthStatus.PASS_CERTIFIED;
        }
        boolean isLiveImageSuccessIdImageAudit = liveImageStatus == FacePlusAuditStatusEnum.SUCCESS && idImageStatus == FacePlusAuditStatusEnum.NEED_AUDIT;
        boolean isLiveImageAuditIdImageSuccess = liveImageStatus == FacePlusAuditStatusEnum.NEED_AUDIT && idImageStatus == FacePlusAuditStatusEnum.SUCCESS;
        boolean isBothAudit = liveImageStatus == FacePlusAuditStatusEnum.NEED_AUDIT && idImageStatus == FacePlusAuditStatusEnum.NEED_AUDIT;
        if (isLiveImageSuccessIdImageAudit || isLiveImageAuditIdImageSuccess || isBothAudit) {
            return UserDataStatusEnum.PortraitAuthStatus.NEED_APPROVE;
        }
        return UserDataStatusEnum.PortraitAuthStatus.NOT_PASS;
    }


    /**
     * 获取当天剩余秒数
     */
    private long getLeftSecondsToday() {
        LocalDate now = new LocalDate();
        return new Duration(now.toDateTimeAtCurrentTime(), now.plusDays(1).toDateTimeAtStartOfDay()).getStandardSeconds();
    }

    @Setter
    @Getter
    private static class FacePlusPortraitRecordExt extends FacePlusPortraitRecord {

        private String errorCode;

        private String errorMessage;

    }

    /**
     * 获取face++图片处理响应函数
     *
     * @param isLiveImage 是否活体图片
     * @param configDTO face++配置
     */
    private BiFunction<FaceIdVerifyResp, Throwable, FacePlusPortraitRecordExt> getFacePlusImageHandleFunction(boolean isLiveImage, final OcrPortraitAuthConfigResponseDTO configDTO) {
        return (resp, throwable) -> {
            FacePlusPortraitRecordExt record = new FacePlusPortraitRecordExt();
            // 异常情况
            if (throwable != null) {
                FacePlusAuditStatusEnum status;
                if (throwable instanceof FaceAuthException) {
                    FaceAuthException faceAuthException = ((FaceAuthException) throwable);
                    String errorMessage = Optional.ofNullable(faceAuthException.getArgs()).filter(args -> args.length > 0).map(args -> (String)args[0]).orElse("");
                    logger.info("face++肖像认证###对比照片###失败 code={}, message={}", faceAuthException.getErrorCode(), errorMessage);
                    record.setErrorCode(faceAuthException.getErrorCode());
                    record.setErrorMessage(errorMessage);
                    if (isMatchFacePlusFailureErrorCode(faceAuthException.getErrorCode())) {
                        status = FacePlusAuditStatusEnum.FAILURE;
                        //  统计肖像认证失败次数
                        portraitStatisticsService.incrFailTimes(PortraitChannelEnum.FACE_PLUS, isLiveImage ? PortraitCompareTypeEnum.LIVE_IMAGE_COMPARE : PortraitCompareTypeEnum.ID_CARD_FRONT_IMAGE_COMPARE);
                    } else if (isMatchFacePlusServerExceptionErrorCode(faceAuthException.getErrorCode())) {
                        // 统计肖像认证异常次数
                        status = FacePlusAuditStatusEnum.SERVER_EXCEPTION;
                        portraitStatisticsService.incrExceptionTimes(PortraitChannelEnum.FACE_PLUS, isLiveImage ? PortraitCompareTypeEnum.LIVE_IMAGE_COMPARE : PortraitCompareTypeEnum.ID_CARD_FRONT_IMAGE_COMPARE);
                    } else {
                        status = FacePlusAuditStatusEnum.INIT; // 未知异常
                        logger.error(String.format("face++肖像认证###对比照片###未知业务异常1, exceptionMessage=%s", throwable.getMessage()), throwable);
                    }
                } else {
                    status = FacePlusAuditStatusEnum.INIT; // 未知异常
                    logger.error(String.format("face++肖像认证###对比照片###未知异常2, exceptionMessage=%s", throwable.getMessage()), throwable);
                }
                if (isLiveImage) {
                    record.setLiveImageAuthStatus(status.getValue());
                } else {
                    record.setIdImageAuthStatus(status.getValue());
                }
            } else {
                logger.info("face++肖像认证###对比成功###对比结果, resp={}", JSON.toJSONString(resp));
                portraitStatisticsService.incrSuccessTimes(PortraitChannelEnum.FACE_PLUS, isLiveImage ? PortraitCompareTypeEnum.LIVE_IMAGE_COMPARE : PortraitCompareTypeEnum.ID_CARD_FRONT_IMAGE_COMPARE);
                FaceIdVerifyResp.ResultFaceid resultFaceid = resp.getResultFaceid();
                FaceIdVerifyResp.Thresholds thresholds = resultFaceid.getThresholds();
                if (isLiveImage) {
                    // 置信度
                    record.setLiveImageConfidence(Double.valueOf(resultFaceid.getConfidence()));
                    // “1e-3”：误识率为千分之一的置信度阈值；
                    record.setLiveImageThreshold1e3(Double.valueOf(thresholds.getOne_e_3()));
                    // “1e-4”：误识率为万分之一的置信度阈值;
                    record.setLiveImageThreshold1e4(Double.valueOf(thresholds.getOne_e_4()));
                    // “1e-5”：误识率为十万分之一的置信度阈值;
                    record.setLiveImageThreshold1e5(Double.valueOf(thresholds.getOne_e_5()));
                    // “1e-6”：误识率为百万分之一的置信度阈值。
                    record.setLiveImageThreshold1e6(Double.valueOf(thresholds.getOne_e_6()));
                    // 配置的置信度区间
                    record.setLiveImageMaxConfigConfidence(Double.valueOf(configDTO.getPortraitAuthFacePlusLiveImageThresholdMax()));
                    record.setLiveImageMinConfigConfidence(Double.valueOf(configDTO.getPortraitAuthFacePlusLiveImageThresholdMin()));
                    //face++人脸照片为软件合成脸的置信度阈值
                    Double syntheticFaceThreshold = Double.valueOf(configDTO.getPortraitAuthFacePlusSyntheticFaceThreshold());
                    //face++人脸照片为面具的置信度阈值
                    Double maskThreshold = Double.valueOf(configDTO.getPortraitAuthFacePlusMaskThreshold());
                    //face++人脸照片为面具的置信度阈值
                    Double screenReplayThreshold = Double.valueOf(configDTO.getPortraitAuthFacePlusScreenReplayThreshold());
                    FaceIdVerifyResp.FaceGenuineness faceGenuineness = resp.getFaceGenuineness();
                    // 软件合成脸的置信度
                    record.setSyntheticFaceConfidence(Double.valueOf(faceGenuineness.getSyntheticFaceConfidence()));
                    // record.setSyntheticFaceThreshold(Double.valueOf(faceGenuineness.getSyntheticFaceThreshold()));
                    record.setSyntheticFaceThreshold(syntheticFaceThreshold);
                    // 面具的置信度
                    record.setMaskConfidence(Double.valueOf(faceGenuineness.getMaskConfidence()));
                    // record.setMaskThreshold(Double.valueOf(faceGenuineness.getMaskThreshold()));
                    record.setMaskThreshold(maskThreshold);
                    // 屏幕翻拍的置信度
                    record.setScreenReplayConfidence(Double.valueOf(faceGenuineness.getScreenReplayConfidence()));
                    // record.setScreenReplayThreshold(Double.valueOf(faceGenuineness.getScreenReplayThreshold()));
                    record.setScreenReplayThreshold(screenReplayThreshold);
                    // synthetic_face_confidence < synthetic_face_threshold，可以认为人脸不是软件合成脸。
                    if (record.getSyntheticFaceConfidence() >= syntheticFaceThreshold) {
                        record.setLiveImageAuthStatus(FacePlusAuditStatusEnum.FAILURE.getValue());
                        // mask_confidence < mask_threshold，可以认为人脸不是面具。
                    } else if (record.getMaskConfidence() >= maskThreshold) {
                        record.setLiveImageAuthStatus(FacePlusAuditStatusEnum.FAILURE.getValue());
                        // screen_replay_confidence < screen_replay_threshold，可以认为人脸不是屏幕翻拍。
                    } else if (record.getScreenReplayConfidence() >= screenReplayThreshold) {
                        record.setLiveImageAuthStatus(FacePlusAuditStatusEnum.FAILURE.getValue());
                        // 大于最大置信度
                    } else if (record.getLiveImageConfidence() >= record.getLiveImageMaxConfigConfidence()) {
                        record.setLiveImageAuthStatus(FacePlusAuditStatusEnum.SUCCESS.getValue());
                        // 大于于最小配置置信度
                    } else if (record.getLiveImageConfidence() >= record.getLiveImageMinConfigConfidence()) {
                        record.setLiveImageAuthStatus(FacePlusAuditStatusEnum.NEED_AUDIT.getValue());
                    } else {
                        record.setLiveImageAuthStatus(FacePlusAuditStatusEnum.FAILURE.getValue());
                    }
                    return record;
                } else {
                    // 置信度
                    record.setIdImageConfidence(Double.valueOf(resultFaceid.getConfidence()));
                    // 配置的置信度区间
                    record.setIdImageMaxConfigConfidence(Double.valueOf(configDTO.getPortraitAuthFacePlusIdImageThresholdMax()));
                    record.setIdImageMinConfigConfidence(Double.valueOf(configDTO.getPortraitAuthFacePlusIdImageThresholdMin()));
                    // “1e-3”：误识率为千分之一的置信度阈值；
                    record.setIdImageThreshold1e3(Double.valueOf(thresholds.getOne_e_3()));
                    // “1e-4”：误识率为万分之一的置信度阈值;
                    record.setIdImageThreshold1e4(Double.valueOf(thresholds.getOne_e_4()));
                    // “1e-5”：误识率为十万分之一的置信度阈值;
                    record.setIdImageThreshold1e5(Double.valueOf(thresholds.getOne_e_5()));
                    // “1e-6”：误识率为百万分之一的置信度阈值。
                    record.setIdImageThreshold1e6(Double.valueOf(thresholds.getOne_e_6()));
                    // 检测出的一张人脸图像的质量
                    record.setIdImageQuality(Double.valueOf(resp.getFaces().get(0).getQuality()));
                    record.setIdImageQualityThreshold(Double.valueOf(resp.getFaces().get(0).getQualityThreshold()));
                    // "id_attacked"：Int类型，判别身份证号码是否曾被冒用来攻击FaceID活体检测，取值1表示曾被攻击、取值0表示未被攻击。
                    record.setIdCardAttacked(resp.getIdExceptions().getIdAttacked().byteValue());
                    // 大于最大置信度
                    if (record.getIdImageConfidence() >= record.getIdImageMaxConfigConfidence()) {
                        record.setIdImageAuthStatus(FacePlusAuditStatusEnum.SUCCESS.getValue());
                        // 大于于最小配置置信度
                    } else if (record.getIdImageConfidence() >= record.getIdImageMinConfigConfidence()) {
                        record.setIdImageAuthStatus(FacePlusAuditStatusEnum.NEED_AUDIT.getValue());
                    } else {
                        record.setIdImageAuthStatus(FacePlusAuditStatusEnum.FAILURE.getValue());
                    }
                    return record;
                }
            }
            return record;
        };
    }

    /**
     * 合并结果函数
     * @param userId 用户id
     * @param identityInfo 用户身份信息
     * @param portraitAuthResponseDTO 肖像认证响应
     * @param record 肖像认证记录
     * @return
     */
    private BiConsumer<FacePlusPortraitRecordExt, FacePlusPortraitRecordExt> getCombinedHandleFunction(String userId, IdentityInfo identityInfo, PortraitAuthResponseDTO portraitAuthResponseDTO, FacePlusPortraitRecord record) {
        return (liveImageRecord, frontImageRecord) -> {
            // 更新记录
            FacePlusPortraitRecord updateRecord = new FacePlusPortraitRecord();
            updateRecord.setId(record.getId());
            updateRecord.setUserId(record.getUserId());
            updateRecord.setLiveImageConfidence(liveImageRecord.getLiveImageConfidence());
            updateRecord.setLiveImageMaxConfigConfidence(liveImageRecord.getLiveImageMaxConfigConfidence());
            updateRecord.setLiveImageMinConfigConfidence(liveImageRecord.getLiveImageMinConfigConfidence());
            updateRecord.setLiveImageThreshold1e3(liveImageRecord.getLiveImageThreshold1e3());
            updateRecord.setLiveImageThreshold1e4(liveImageRecord.getLiveImageThreshold1e4());
            updateRecord.setLiveImageThreshold1e5(liveImageRecord.getLiveImageThreshold1e5());
            updateRecord.setLiveImageThreshold1e6(liveImageRecord.getLiveImageThreshold1e6());
            updateRecord.setSyntheticFaceConfidence(liveImageRecord.getSyntheticFaceConfidence());
            updateRecord.setSyntheticFaceThreshold(liveImageRecord.getSyntheticFaceThreshold());
            updateRecord.setMaskConfidence(liveImageRecord.getMaskConfidence());
            updateRecord.setMaskThreshold(liveImageRecord.getMaskThreshold());
            updateRecord.setScreenReplayConfidence(liveImageRecord.getScreenReplayConfidence());
            updateRecord.setScreenReplayThreshold(liveImageRecord.getScreenReplayThreshold());
            updateRecord.setLiveImageAuthStatus(liveImageRecord.getLiveImageAuthStatus());
            updateRecord.setIdImageConfidence(frontImageRecord.getIdImageConfidence());
            updateRecord.setIdImageMaxConfigConfidence(frontImageRecord.getIdImageMaxConfigConfidence());
            updateRecord.setIdImageMinConfigConfidence(frontImageRecord.getIdImageMinConfigConfidence());
            updateRecord.setIdImageThreshold1e3(frontImageRecord.getIdImageThreshold1e3());
            updateRecord.setIdImageThreshold1e4(frontImageRecord.getIdImageThreshold1e4());
            updateRecord.setIdImageThreshold1e5(frontImageRecord.getIdImageThreshold1e5());
            updateRecord.setIdImageThreshold1e6(frontImageRecord.getIdImageThreshold1e6());
            updateRecord.setIdImageQuality(frontImageRecord.getIdImageQuality());
            updateRecord.setIdImageQualityThreshold(frontImageRecord.getIdImageQualityThreshold());
            updateRecord.setIdCardAttacked(frontImageRecord.getIdCardAttacked());
            updateRecord.setIdImageAuthStatus(frontImageRecord.getIdImageAuthStatus());
            logger.info("face++对比###历史更新记录###record={}", JSON.toJSONString(updateRecord));
            // 认证记录
            Optional<FacePlusPortrait> oldFacePlusPortraitOpt = Optional.ofNullable(facePlusPortraitService.getFacePlusPortraitByUserId(record.getUserId()));
            FacePlusPortrait updateFacePlusPortrait = new FacePlusPortrait();
            updateFacePlusPortrait.setUserId(record.getUserId());
            updateFacePlusPortrait.setBestLiveImage(record.getBestLiveImage());
            updateFacePlusPortrait.setLiveImageMaxConfigConfidence(liveImageRecord.getLiveImageMaxConfigConfidence());
            updateFacePlusPortrait.setLiveImageMinConfigConfidence(liveImageRecord.getLiveImageMinConfigConfidence());
            updateFacePlusPortrait.setLiveImageConfidence(liveImageRecord.getLiveImageConfidence());
            updateFacePlusPortrait.setLiveImageThreshold1e3(liveImageRecord.getLiveImageThreshold1e3());
            updateFacePlusPortrait.setLiveImageThreshold1e4(liveImageRecord.getLiveImageThreshold1e4());
            updateFacePlusPortrait.setLiveImageThreshold1e5(liveImageRecord.getLiveImageThreshold1e5());
            updateFacePlusPortrait.setLiveImageThreshold1e6(liveImageRecord.getLiveImageThreshold1e6());
            updateFacePlusPortrait.setSyntheticFaceConfidence(liveImageRecord.getSyntheticFaceConfidence());
            updateFacePlusPortrait.setSyntheticFaceThreshold(liveImageRecord.getSyntheticFaceThreshold());
            updateFacePlusPortrait.setMaskConfidence(liveImageRecord.getMaskConfidence());
            updateFacePlusPortrait.setMaskThreshold(liveImageRecord.getMaskThreshold());
            updateFacePlusPortrait.setScreenReplayConfidence(liveImageRecord.getScreenReplayConfidence());
            updateFacePlusPortrait.setScreenReplayThreshold(liveImageRecord.getScreenReplayThreshold());
            updateFacePlusPortrait.setLiveImageAuthStatus(liveImageRecord.getLiveImageAuthStatus());
            updateFacePlusPortrait.setIdImage(record.getIdImage());
            updateFacePlusPortrait.setIdImageConfidence(frontImageRecord.getIdImageConfidence());
            updateFacePlusPortrait.setIdImageMaxConfigConfidence(frontImageRecord.getIdImageMaxConfigConfidence());
            updateFacePlusPortrait.setIdImageMinConfigConfidence(frontImageRecord.getIdImageMinConfigConfidence());
            updateFacePlusPortrait.setIdImageThreshold1e3(frontImageRecord.getIdImageThreshold1e3());
            updateFacePlusPortrait.setIdImageThreshold1e4(frontImageRecord.getIdImageThreshold1e4());
            updateFacePlusPortrait.setIdImageThreshold1e5(frontImageRecord.getIdImageThreshold1e5());
            updateFacePlusPortrait.setIdImageThreshold1e6(frontImageRecord.getIdImageThreshold1e6());
            updateFacePlusPortrait.setIdImageQuality(frontImageRecord.getIdImageQuality());
            updateFacePlusPortrait.setIdImageQualityThreshold(frontImageRecord.getIdImageQualityThreshold());
            updateFacePlusPortrait.setIdCardAttacked(frontImageRecord.getIdCardAttacked());
            updateFacePlusPortrait.setIdImageAuthStatus(frontImageRecord.getIdImageAuthStatus());
            if (!oldFacePlusPortraitOpt.isPresent()){
                updateFacePlusPortrait.setCreateTime(new Date());
            }
            logger.info("face++对比###认证更新记录###record={}", JSON.toJSONString(updateFacePlusPortrait));
            // 状态记录
            List<DataStatus> list = Optional.ofNullable(userStatusService.getDataStatusByUserId(record.getUserId())).orElse(Collections.EMPTY_LIST);
            // 获取肖像认证状态
            UserDataStatusEnum.PortraitAuthStatus portraitAuthStatus = getCombinedPortraitAduitStatus(updateFacePlusPortrait.getLiveImageAuthStatus(), updateFacePlusPortrait.getIdImageAuthStatus());
            // 肖像认证状态记录
            Optional<DataStatus> oldPortraitAuthStatusOpt = list.stream()
                                                                .filter(s -> Objects.equals(s.getDataType(), UserDataTypeEnum.PORTRAIT.getValue()))
                                                                .findFirst();
            DataStatus updatePortraitStatus = generateUpdateDataStatus(oldPortraitAuthStatusOpt, record.getUserId(), UserDataTypeEnum.PORTRAIT.getValue(), portraitAuthStatus.getStatus());
            // 认证渠道信息
            UserAuthorizationInfo updateAuthorizationInfo = new UserAuthorizationInfo();
            updateAuthorizationInfo.setUserId(record.getUserId());
            updateAuthorizationInfo.setFaceVerificationChannel(PortraitChannelEnum.FACE_PLUS.getValue());
            // 是否认证成功
            boolean isPortraitAuthSuccess = portraitAuthStatus == UserDataStatusEnum.PortraitAuthStatus.PASS_CERTIFIED || portraitAuthStatus == UserDataStatusEnum.PortraitAuthStatus.NEED_APPROVE;
            // 二要素状态
            Optional<DataStatus> oldTwoElementStatusOpt = list.stream()
                    .filter(s -> Objects.equals(s.getDataType(), UserDataTypeEnum.TWO_ELEMENT.getValue()))
                    .findFirst();
            // 肖像认证成功但是二要素状态不存在或未认证， 则需要更新二要素状态
            boolean isUpdateTwoElementStatus = isPortraitAuthSuccess && (!oldTwoElementStatusOpt.isPresent() || Objects.equals(oldTwoElementStatusOpt.get().getDataStatus(), UserDataStatusEnum.RealNameAuthStatus.NOT_CERTIFIED.getStatus()));
            DataStatus updateTwoElementStatus = new DataStatus();
            IdentityMapping identityMapping = new IdentityMapping();
            if (isUpdateTwoElementStatus) {
                updateTwoElementStatus = generateUpdateDataStatus(oldTwoElementStatusOpt, record.getUserId(), UserDataTypeEnum.TWO_ELEMENT.getValue(), UserDataStatusEnum.RealNameAuthStatus.PASS_CERTIFIED.getStatus());
                identityMapping.setUserId(record.getUserId());
                identityMapping.setIdCard(identityInfo.getIdCard());
                identityMapping.setCreateTime(new Date());
                mobileIdentityService.insertMappingData(record.getUserId());
            }
            logger.info("face++对比###二要素记录###change={}, record={}", isUpdateTwoElementStatus, JSON.toJSONString(updateTwoElementStatus));
            if (!facePlusPortraitService.saveAuthAndHistoryRecord(updateRecord, oldFacePlusPortraitOpt.isPresent(), updateFacePlusPortrait, oldPortraitAuthStatusOpt.isPresent(), updatePortraitStatus, updateAuthorizationInfo, isUpdateTwoElementStatus, oldTwoElementStatusOpt.isPresent(), updateTwoElementStatus, identityMapping)) {
                throw new BizException(LoginAccountErrorCode.SAVE_PORTRAIT_AUTH_FAIL);
            }
            if (isUpdateTwoElementStatus) {

                UserRealNameReqDTO json = new UserRealNameReqDTO();
                json.setUserId(record.getUserId());
                json.setIdentityCard(identityInfo.getIdCard());
                json.setRealNameDate(new Date());
                json.setUpdateDate(new Date());
                logger.info("身份认证###标签-实名认证, data={}", JSONObject.toJSONString(json));
                rabbitTemplate.convertAndSend(BizEventRabbitConstant.Exchange.BIZ_EVENT_DIRECT_EXCHANGE, BizEventRabbitConstant.Key.BIZ_EVENT_USER_REAL_NAME_KEY, json);

                UpdateUserInfoRequest request = new UpdateUserInfoRequest();
                request.setGuid(record.getUserId());
                request.setAddIdentity(1);
                logger.info("肖像认证成功###调用IM###请求， request={}", JSON.toJSONString(request));
                CompletableFuture.runAsync(() -> imService.updateUserInfo(request));
            }
                 /*
                117001:照片中未检测出人脸，前端需要提示出具体错误给前端（400：IMAGE_ERROR_UNSUPPORTED_FORMAT: <param>；NO_FACE_FOUND: <param>
                        DATA_VALIDATION_ERROR）

                117002:抱歉，您的肖像认证超时，请重试一次（APP定义为30秒为超时）

                117003:抱歉，认证失败，请再试一次（400：MULTIPLE_FACES: <param>；INVALID_IMAGE_SIZE: <param>；
                INVALID_FACE_TOKEN: <token>）

                117004:抱歉，姓名与身份证不匹配，肖像认证失败（400：NO_SUCH_ID_NUMBER；ID_NUMBER_NAME_NOT_MATCH；
                INVALID_NAME_FORMAT；INVALID_IDCARD_NUMBER）

                117005:抱歉，您的肖像存在异常，请重新采集（400：）

                117006:抱歉，认证通道异常，请切换通道（400：
                DATA_SOURCE_ERROR；）
                */
            if (isNoFaceFound(liveImageRecord.getErrorMessage()) || isNoFaceFound(frontImageRecord.getErrorMessage())) {
                throw new BizException(LoginAccountErrorCode.CHANNEL_NO_FACE_FOUND);
            }
            if (isAuthFail(liveImageRecord.getErrorMessage()) || isAuthFail(frontImageRecord.getErrorMessage())) {
                throw new BizException(LoginAccountErrorCode.CHANNEL_AUTH_FAIL);
            }
            if (isNotMatchIdentity(liveImageRecord.getErrorMessage()) || isNotMatchIdentity(frontImageRecord.getErrorMessage())) {
                throw new BizException(LoginAccountErrorCode.CHANNEL_NOT_MATCH_IDENTITY);
            }
            if (isChannelException(liveImageRecord.getErrorMessage()) || isChannelException(frontImageRecord.getErrorMessage())) {
                throw new BizException(LoginAccountErrorCode.CHANNEL_NEED_TO_CHANGE);
            }
            boolean isLiveImageMatch400 = liveImageRecord.getErrorCode() != null && liveImageRecord.getErrorCode().equals("400");
            boolean isIdImageMatch400 = frontImageRecord.getErrorCode() != null && frontImageRecord.getErrorCode().equals("400");
            if (isLiveImageMatch400 || isIdImageMatch400) {
                throw new BizException(LoginAccountErrorCode.CHANNEL_PORTRAIT_EXCEPTION);
            }
            // 认证不通过，增加错误次数
            int errorTimes = 0;
            if (!isPortraitAuthSuccess) {
                errorTimes = facePlusPortraitService.incrCacheValue(PORTRAIT_AUTH_ERROR_TIMES_FORMAT, new Object[]{record.getUserId()}, getLeftSecondsToday(), TimeUnit.SECONDS).map(Long::intValue).orElse(0);
            }
            portraitAuthResponseDTO.setLeftPortraitAuthTimes(Math.max(0, getPortraitAuthTimesThreshold() - errorTimes));
            portraitAuthResponseDTO.setPortraitAuthStatus(portraitAuthStatus);
        };
    }

    private DataStatus generateUpdateDataStatus(Optional<DataStatus> oldDataStatusOpt, String userId, byte dataType, byte status) {
        DataStatus updateStatus = new DataStatus();
        updateStatus.setUserId(userId);
        updateStatus.setDataType(dataType);
        updateStatus.setDataStatus(status);
        updateStatus.setAuthTime(new Date());
        if (!oldDataStatusOpt.isPresent()) {
            updateStatus.setId(snowflakeIdWorker.nextId());
            updateStatus.setCreateTime(new Date());
        }
        return updateStatus;
    }


    /**
     * 是否命中face++失败错误码
     *
     * @param errorCode face++错误码
     */
    private boolean isMatchFacePlusFailureErrorCode(String errorCode) {
        return Arrays.asList("400", "403", "413", "414").contains(errorCode);
    }

    /**
     * 是否命中face++异常错误码
     *
     * @param errorCode face++错误码
     */
    private boolean isMatchFacePlusServerExceptionErrorCode(String errorCode) {
        return Collections.singleton("500").contains(errorCode);
    }

    /**
     * 创建face++身份证照片对比请求
     *
     * @param identityInfo 身份信息
     * @param imgUrl 正面照URL
     */
    private FaceIdVerifyReq createFacePlusIdImageRequest(IdentityInfo identityInfo, String imgUrl) {
        FaceIdVerifyReq idImageVerifyReq = new FaceIdVerifyReq();
        idImageVerifyReq.setIdcardName(identityInfo.getName());
        idImageVerifyReq.setIdcardNumber(decrypIdCard(identityInfo.getIdCard()));
        idImageVerifyReq.setComparisonType(ComparisonTypeEnum.SOURCE);
        idImageVerifyReq.setFaceImageType(FaceImageTypeEnum.RAW_IMAGE);
        FaceIdVerifyReq.FaceIdVerifyOnePicInfo idImageInfo = new FaceIdVerifyReq.FaceIdVerifyOnePicInfo();
        idImageInfo.setImage(downloadFile(imgUrl).join());
        idImageInfo.setFaceQualityThreshold("0");
        idImageInfo.setReturnFaces("1");
        idImageVerifyReq.setPicInfo(idImageInfo);
        logger.info("face++对比###身份证照片###请求参数###request={}", JSON.toJSONString(idImageVerifyReq));
        return idImageVerifyReq;
    }

    /**
     * 创建face++活体照片对比请求
     *
     * @param identityInfo 身份信息
     * @param delta face++校验字符串
     * @param liveImageUrl 活体照片URL
     * @param envImageUrl 对比照片URL
     */
    private FaceIdVerifyReq createFacePlusLiveImageRequest(IdentityInfo identityInfo, String delta, String liveImageUrl, String envImageUrl) {
        FaceIdVerifyReq liveImageVerifyReq = new FaceIdVerifyReq();
        liveImageVerifyReq.setIdcardName(identityInfo.getName());
        liveImageVerifyReq.setIdcardNumber(decrypIdCard(identityInfo.getIdCard()));
        liveImageVerifyReq.setComparisonType(ComparisonTypeEnum.SOURCE);
        liveImageVerifyReq.setFaceImageType(FaceImageTypeEnum.MEGLIVE);
        FaceIdVerifyReq.FaceIdVerifyMegLiveSDKPicInfo liveImageInfo = new FaceIdVerifyReq.FaceIdVerifyMegLiveSDKPicInfo();
        liveImageInfo.setDelta(delta);
        downloadFile(liveImageUrl).thenAcceptBoth(downloadFile(envImageUrl), (b1, b2) -> {
            liveImageInfo.setImageBest(b1);
            liveImageInfo.setImageEnv(b2);
        }).join();
        liveImageVerifyReq.setPicInfo(liveImageInfo);
        logger.info("face++对比###活体照片###请求参数###request={}", JSON.toJSONString(liveImageVerifyReq));
        return liveImageVerifyReq;
    }

    /**
     * 初始化face++认证历史记录
     *
     * @param userId 用户id
     * @param liveImageUrl 活体照片url
     * @param idImage 身份证正面照url
     * @param delta face++校验字符串
     * @param clientType 客户端类型
     */
    private FacePlusPortraitRecord initFacePlusPortraitRecord(String userId, String liveImageUrl, String idImage, String delta, PortraitAuthClientType clientType) {
        FacePlusPortraitRecord record = new FacePlusPortraitRecord();
        record.setId(snowflakeIdWorker.nextId());
        record.setUserId(userId);
        record.setBestLiveImage(liveImageUrl);
        record.setIdImage(idImage);
        record.setCreateTime(new Date());
        record.setIdImageAuthStatus(FacePlusAuditStatusEnum.INIT.getValue());
        record.setLiveImageAuthStatus(FacePlusAuditStatusEnum.INIT.getValue());
        if (!facePlusPortraitService.saveHistoryRecord(record)) {
            throw new BizException("保存肖像认证信息失败");
        }
        return record;
    }

    /**
     * 解密身份证号
     *
     * @param idCard 身份证号密文
     */
    private String decrypIdCard(String idCard) {
        return privacyMasks.decryptPrivacy(idCard);
    }

    /**
     * 查询本人照片
     * @param userId
     * @return
     */
    public String searchBestLiveImage(String userId) {
        FacePlusPortrait facePlusPortrait = facePlusPortraitService.getFacePlusPortraitByUserId(userId);
        if(facePlusPortrait==null){
            return null;
        }
        return facePlusPortrait.getBestLiveImage();
    }

}

